package controller;

import controller.human.CustomRole;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.TreeSet;
import java.util.Map.Entry;

import controller.human.Action;
import controller.human.Person;
import controller.human.PhysicalState;
import controller.human.Role;
import controller.human.User;
import controller.util.Pair;
import engine.Statistics;
import engine.Statistics2Manager;
import java.util.ArrayList;
import view.Gui;
import view.Gui2Manager;
import views.MafiaFrame;

public class Manager implements Manager2Gui {

    GameSettings settings;
    Gui2Manager gui;
    Set<Person> persons = new TreeSet<Person>();
    private Statistics2Manager statistics;
    private Map<User, Pair<User, Action>> actions = new HashMap<User, Pair<User, Action>>();
    private Map<User, User> votes = new HashMap<User, User>();
    private ArrayList<Role> roles = new ArrayList<Role>();
    private Random r = new Random();

    public Manager(Gui2Manager gui, Statistics2Manager statistics) {
        this.gui = gui;
        this.statistics = statistics;
        statistics.init(this);
        gui.init(this);
    }

    @Override
    public void setSettings(GameSettings settings) {
        this.settings = settings;
    }

    public static void main(String[] args) {
        Gui2Manager gui = new MafiaFrame();
        Statistics2Manager statistics = new Statistics();
        @SuppressWarnings("unused")
        Manager manager = new Manager(gui, statistics);
        gui.init(manager);
    }

    @Override
    public Role addUser(Person person) {
        persons.add((Person) person);
        person.setRole(roles.remove(r.nextInt(roles.size())));
        return person.getRole();
    }

    @Override
    public void changeState(GameState newState) {
        //TODO

        if (GameState.Night == newState) {
            for (Person p : persons) {
                p.setSocialState(0);
            }
            actions = new HashMap<User, Pair<User, Action>>();
            setAllDisplay();
        } else if (GameState.Day == newState) {
            setAllDisplay();
            dealDayState();
        } else if (GameState.Debate == newState) {
            setAllDisplay();
            for (Person p : persons) {
                p.setSocialState(-1);
            }
        } else if (GameState.Voting == newState) {
            votes = new HashMap<User, User>();
            /* Added an isProposed method for displaying only proposed on second screen
            for (Person p: persons) {
            if (p.getPhysicalState()!=PhysicalState.Dead && p.getSocialState()>=0) {
            p.setDisplay(true);
            } else {
            p.setDisplay(false);
            }
            }
             */
            for (Person p : persons) {
                if (p.getPhysicalState() != PhysicalState.Dead && p.getPhysicalState() != PhysicalState.Hand) {
                    p.setDisplay(true);
                } else {
                    p.setDisplay(false);
                }
            }
        } else if (GameState.VoteFinished == newState) {
            User killed = dealVotes();
            if (killed != null) {
                ((Person) killed).setPhysicalState(PhysicalState.Dead);
                ((Person) killed).setDisplay(false);
            }
            statistics.logAction(null, killed, Action.VoteKilled, true);
            gui.voteResults(votes, killed);
        }

    }

    private void setAllDisplay() {
        for (Person p : persons) {
            if (p.getPhysicalState() != PhysicalState.Dead) {
                p.setDisplay(true);
            } else {
                p.setDisplay(false);
            }
        }
    }

    private User dealVotes() {
        // TODO search for the user with most votes and return it
        // if equality return null
        return votes.values().iterator().next();
    }

    private void dealDayState() {
        User killed = null, handicapped = null;
        Action handicappAction = null;

        for (Entry<User, Pair<User, Action>> entry : actions.entrySet()) {
            User actor = entry.getKey();

            if (Role.Killer == getRole(actor)) {
                User target = entry.getValue().getKey();
                Person pt = (Person) target;

                // if person is not dead
                if (PhysicalState.Dead != pt.getPhysicalState()) {
                    // add one more kill vote
                    pt.setSocialState(pt.getSocialState() + 1);
                }
            }
        }

        int max = 0, nrMax = 0;
        for (Person p : persons) {
            if (p.getSocialState() > max) {
                max = p.getSocialState();
                killed = p;
                nrMax = 1;
            } else if (p.getSocialState() == max) {
                nrMax++;
                if ((new Random()).nextDouble() < (1.0 / nrMax)) {
                    killed = p;
                }
            }
        }

        for (Entry<User, Pair<User, Action>> entry : actions.entrySet()) {
            User actor = entry.getKey();

            if (Role.Handicapper == getRole(actor)) {
                User target = entry.getValue().getKey();
                Action action = entry.getValue().getValue();

                handicapped = target;
                handicappAction = action;
            }
        }

        for (Entry<User, Pair<User, Action>> entry : actions.entrySet()) {
            User actor = entry.getKey();

            if (Role.Doctor == getRole(actor)) {
                User target = entry.getValue().getKey();
                Action action = entry.getValue().getValue();

                boolean result = false;
                if (target.equals(killed)) {
                    killed = null;
                    result = true;
                } else if (target.equals(handicapped)) {
                    handicapped = null;
                    result = true;
                }

                statistics.logAction(actor, target, action, result);
            }
        }

        for (Entry<User, Pair<User, Action>> entry : actions.entrySet()) {
            User actor = entry.getKey();
            User target = entry.getValue().getKey();
            Action action = entry.getValue().getValue();

            if (Role.Killer == getRole(actor)) {
                if (target.equals(killed)) {
                    statistics.logAction(actor, target, action, true);
                } else {
                    statistics.logAction(actor, target, action, false);
                }
            } else if (Role.Handicapper == getRole(actor)) {
                if (target.equals(handicapped)) {
                    statistics.logAction(actor, target, action, true);
                } else {
                    statistics.logAction(actor, target, action, false);
                }
            }
        }

        if (killed != null) {
            Person p = (Person) killed;
            p.setPhysicalState(PhysicalState.Dead);
            p.setDisplay(false);
        }

        if (handicapped != null) {
            Person p = (Person) handicapped;
            if (handicappAction == Action.Mute) {
                p.setPhysicalState(PhysicalState.Mute);
            } else {
                p.setPhysicalState(PhysicalState.Hand);
            }
        }

        gui.dayResult(killed, handicapped, handicappAction);
    }

    @Override
    public void startNewSession() {
        for (Person p : persons) {
            p.setDisplay(true);
            p.setPhysicalState(PhysicalState.Alive);
            p.setSocialState(-1);
        }
    }

    @Override
    public boolean nightAction(User actor, User target, Action action) {
        // already did something this night
        if (actions.get(actor) != null) {
            statistics.logAction(actor, target, Action.SecondLogin, true);
            return r.nextBoolean();
        }

        if (getRole(actor) == Role.Cop) {
            if (getRole(target) == Role.Killer) {
                statistics.logAction(actor, target, action, true);
                return true;
            } else {
                statistics.logAction(actor, target, action, false);
                return false;
            }
        } else {
            if (getRole(actor) != Role.Citizen) {
                actions.put(actor, new Pair<User, Action>(target, action));
            }

            return r.nextBoolean();
        }
    }

    public Role getRole(User u) {
        return ((Person) u).getRole();
    }

    @Override
    public boolean existProposal() {
        for (Person p : persons) {
            if (p.getPhysicalState() != PhysicalState.Dead
                    && p.getSocialState() == 0) {
                return true;
            }
        }
        return false;
    }

    @Override
    public void addProposal(User user, User proposed) {
        if (proposed != null && ((Person) proposed).getSocialState() == -1) {
            statistics.logAction(user, proposed, Action.Propose, true);
            ((Person) proposed).setSocialState(0);
        }
    }

    @Override
    public void addVote(User actor, User target) {
        if (target.isProposed()) {
            ((Person) target).incrementSocialState();
            votes.put(actor, target);
            statistics.logAction(actor, target, Action.Vote, true);
        }

    }

    @Override
    public void setRoles(ArrayList<CustomRole> roles) {
        for (CustomRole cr : roles) {
            for (int i = 0; i < cr.numberOfPlayers; i++) {
                this.roles.add(cr.role);
            }
        }
    }

    public void setRol(int k,int c,int d, int h, int citiz)
    {
        for (int i = 0; i < k; i++) {
            this.roles.add(Role.Killer);
        }
        for (int i = 0; i < c; i++) {
            this.roles.add(Role.Cop);
        }
        for (int i = 0; i < d; i++) {
            this.roles.add(Role.Doctor);
        }
        for (int i = 0; i < h; i++) {
            this.roles.add(Role.Handicapper);
        }
        for (int i = 0; i < citiz; i++) {
            this.roles.add(Role.Citizen);
        }
    }
    @Override
    public void setRoles(int nrPlayers) {
        if (nrPlayers < 12 && nrPlayers > 7) {
            setRol(2, 1, 1, 2, nrPlayers -6);
        }
        if (nrPlayers < 22 && nrPlayers > 12) {
            setRol(3, 1, 1, 2, nrPlayers -7);
        }
         if (nrPlayers < 32 && nrPlayers > 22) {
            setRol(4, 1, 1, 3, nrPlayers -9);
        }
         else {
            int k= (int)Math.sqrt(nrPlayers);
            setRol(k, 1, 1, 2, nrPlayers -(4+k));
        }
    }
    public int gameEnded() {
        int nrKillers=0;
        int nrGood =0;
        for (Person p : persons) {
            if (p.getPhysicalState() != PhysicalState.Dead) {
                if(p.getRole()==Role.Killer)
                    nrKillers+=1;
                else
                    nrGood+=1;                
            }
        }
        if(nrKillers == 0 && nrGood >0)
            return 1;
        if(nrKillers>0 && nrGood<=2)
            return -1;
        return 0;
    }
}
